package cn.sciento.hitoa.thread;

import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.Arrays;

public final class ThreadUtilities {
    private static final String NULL_NAME = "Null name";

    private static ThreadGroup rootThreadGroup = null;

    public static ThreadGroup getRootThreadGroup() {
        if (rootThreadGroup != null)
            return rootThreadGroup;
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        ThreadGroup ptg;
        while ((ptg = tg.getParent()) != null)
            tg = ptg;
        rootThreadGroup = tg;
        return tg;
    }

    public static ThreadGroup[] getAllThreadGroups() {
        ThreadGroup root = getRootThreadGroup();
        int nAlloc = root.activeGroupCount();
        int n = 0;
        ThreadGroup[] groups = null;
        while (true) {
            nAlloc *= 2;
            groups = new ThreadGroup[nAlloc];
            n = root.enumerate(groups, true);
            if (n != nAlloc) {
                ThreadGroup[] allGroups = new ThreadGroup[n + 1];
                allGroups[0] = root;
                System.arraycopy(groups, 0, allGroups, 1, n);
                return allGroups;
            }
        }
    }

    public static ThreadGroup getThreadGroup(String name) {
        if (name == null)
            throw new NullPointerException("Null name");
        ThreadGroup[] groups = getAllThreadGroups();
        for (ThreadGroup group : groups) {
            if (group.getName().equals(name))
                return group;
        }
        return null;
    }

    public static Thread[] getAllThreads() {
        ThreadGroup root = getRootThreadGroup();
        ThreadMXBean thbean = ManagementFactory.getThreadMXBean();
        int nAlloc = thbean.getThreadCount();
        int n = 0;
        Thread[] threads = null;
        while (true) {
            nAlloc *= 2;
            threads = new Thread[nAlloc];
            n = root.enumerate(threads, true);
            if (n != nAlloc)
                return Arrays.<Thread>copyOf(threads, n);
        }
    }

    public static Thread[] getGroupThreads(ThreadGroup group) {
        if (group == null)
            throw new NullPointerException("Null group");
        int nAlloc = group.activeCount();
        int n = 0;
        Thread[] threads = null;
        while (true) {
            nAlloc *= 2;
            threads = new Thread[nAlloc];
            n = group.enumerate(threads, false);
            if (n != nAlloc)
                return Arrays.<Thread>copyOf(threads, n);
        }
    }

    public static Thread[] getGroupThreads(String name) {
        ThreadGroup group = getThreadGroup(name);
        if (group == null)
            return new Thread[0];
        return getGroupThreads(group);
    }

    public static Thread[] getAllThreadsPrioritized() {
        Thread[] allThreads = getAllThreads();
        Arrays.sort(allThreads, (t1, t2) -> t2.getPriority() - t1.getPriority());
        return allThreads;
    }

    public static Thread[] getAllDaemonThreads() {
        Thread[] allThreads = getAllThreads();
        Thread[] daemons = new Thread[allThreads.length];
        int nDaemon = 0;
        for (Thread thread : allThreads) {
            if (thread.isDaemon())
                daemons[nDaemon++] = thread;
        }
        return Arrays.<Thread>copyOf(daemons, nDaemon);
    }

    public static Thread[] getAllThreads(Thread.State state) {
        Thread[] allThreads = getAllThreads();
        Thread[] found = new Thread[allThreads.length];
        int nFound = 0;
        for (Thread thread : allThreads) {
            if (thread.getState() == state)
                found[nFound++] = thread;
        }
        return Arrays.<Thread>copyOf(found, nFound);
    }

    public static Thread getThread(String name) {
        if (name == null)
            throw new NullPointerException("Null name");
        Thread[] threads = getAllThreads();
        for (Thread thread : threads) {
            if (thread.getName().equals(name))
                return thread;
        }
        return null;
    }

    public static Thread getThread(long id) {
        Thread[] threads = getAllThreads();
        for (Thread thread : threads) {
            if (thread.getId() == id)
                return thread;
        }
        return null;
    }

    public static Thread getThread(ThreadInfo info) {
        if (info == null)
            throw new NullPointerException("Null info");
        return getThread(info.getThreadId());
    }

    public static ThreadInfo[] getAllThreadInfos() {
        ThreadInfo[] infos;
        ThreadMXBean thbean = ManagementFactory.getThreadMXBean();
        long[] ids = thbean.getAllThreadIds();
        if (!thbean.isObjectMonitorUsageSupported() ||
                !thbean.isSynchronizerUsageSupported()) {
            infos = thbean.getThreadInfo(ids);
        } else {
            infos = thbean.getThreadInfo(ids, true, true);
        }
        ThreadInfo[] notNulls = new ThreadInfo[infos.length];
        int nNotNulls = 0;
        for (ThreadInfo info : infos) {
            if (info != null)
                notNulls[nNotNulls++] = info;
        }
        if (nNotNulls == infos.length)
            return infos;
        return Arrays.<ThreadInfo>copyOf(notNulls, nNotNulls);
    }

    public static ThreadInfo getThreadInfo(String name) {
        if (name == null)
            throw new NullPointerException("Null name");
        Thread[] threads = getAllThreads();
        for (Thread thread : threads) {
            if (thread.getName().equals(name))
                return getThreadInfo(thread.getId());
        }
        return null;
    }

    public static ThreadInfo getThreadInfo(long id) {
        ThreadMXBean thbean = ManagementFactory.getThreadMXBean();
        if (!thbean.isObjectMonitorUsageSupported() ||
                !thbean.isSynchronizerUsageSupported())
            return thbean.getThreadInfo(id);
        ThreadInfo[] infos = thbean.getThreadInfo(new long[]{id}, true, true);
        if (infos.length == 0)
            return null;
        return infos[0];
    }

    public static ThreadInfo getThreadInfo(Thread thread) {
        if (thread == null)
            throw new NullPointerException("Null thread");
        return getThreadInfo(thread.getId());
    }

    public static Thread getLockingThread(Object object) {
        if (object == null)
            throw new NullPointerException("Null object");
        long identity = System.identityHashCode(object);
        Thread[] allThreads = getAllThreads();
        ThreadInfo info = null;
        MonitorInfo[] monitors = null;
        for (Thread thread : allThreads) {
            info = getThreadInfo(thread.getId());
            if (info != null) {
                monitors = info.getLockedMonitors();
                for (MonitorInfo monitor : monitors) {
                    if (identity == monitor.getIdentityHashCode())
                        return thread;
                }
            }
        }
        return null;
    }

    public static Thread getBlockingThread(Thread blockedThread) {
        ThreadInfo info = getThreadInfo(blockedThread);
        if (info == null)
            return null;
        long id = info.getLockOwnerId();
        if (id == -1L)
            return null;
        return getThread(id);
    }
}
